aboutsummaryrefslogtreecommitdiff
path: root/website/src/pages/releases/[...slug].astro
blob: 420d4b9e7d472e02aeb22e4672671d3344d69ee8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
---
import { getCollection, render } from 'astro:content';
import MarketingLayout from '../../components/MarketingLayout.astro';

export async function getStaticPaths() {
  const releases = await getCollection('releases');
  return releases
    .filter((release) => !release.data.draft)
    .map((release) => {
      const slug = release.data.path;
      return {
        params: { slug },
        props: { release, slug },
      };
    });
}

const { release, slug } = Astro.props;
const { Content, headings } = await render(release);
const sectionHeadings = headings.filter((heading) => heading.depth === 2);
---

<MarketingLayout
  title={`${release.data.version}: ${release.data.title} — Herdr releases`}
  description={release.data.description}
  canonicalPath={`/releases/${slug}/`}
  ogType="article"
>
  <main class="content-shell">
    <div class="article-layout release-layout section">
      <aside class="article-toc" aria-label="Release sections">
        <p>Sections</p>
        <nav>
          {sectionHeadings.map((heading) => (
            <a href={`#${heading.slug}`} data-heading-link={heading.slug}>{heading.text}</a>
          ))}
        </nav>
      </aside>

      <article class="article-shell release-shell">
        <header class="article-header release-header">
          <p class="post-meta">
            <a href="/releases/">Releases</a>
            <span>{release.data.version}</span>
            <time datetime={release.data.date.toISOString()}>{release.data.date.toLocaleDateString('en', {
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })}</time>
          </p>
          <h1>{release.data.title}</h1>
          <p>{release.data.description}</p>
        </header>
        <div class="article-prose release-prose">
          <Content />
        </div>
      </article>
    </div>
  </main>

  <script>
    const headings = Array.from(document.querySelectorAll('.article-prose h2[id]'));
    const links = new Map(
      Array.from(document.querySelectorAll('[data-heading-link]')).map((link) => [
        link.getAttribute('data-heading-link'),
        link,
      ]),
    );

    function setActive(id) {
      for (const link of links.values()) link.removeAttribute('aria-current');
      links.get(id)?.setAttribute('aria-current', 'true');
    }

    if (headings.length > 0 && links.size > 0) {
      setActive(headings[0].id);
      const observer = new IntersectionObserver(
        (entries) => {
          const visible = entries
            .filter((entry) => entry.isIntersecting)
            .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
          if (visible?.target instanceof HTMLElement) setActive(visible.target.id);
        },
        { rootMargin: '-18% 0px -68% 0px', threshold: 0 },
      );
      for (const heading of headings) observer.observe(heading);
    }
  </script>
</MarketingLayout>